﻿#include "pch.h"
#include "Game.h"

/**
 * 构造方法。
 * @param callback  回调
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 */
Game::Game(CWnd* callback, CDC* drawer, CWnd* gameBoard) :
    mBlackBrush(RGB(0, 0, 0)),
    mCallback(callback),
    mDrawer(drawer),
    mGameBoard(gameBoard),
    mIsBlack(TRUE),
    mStatus(END),
    mWhiteBrush(RGB(255, 255, 255)) {
    // 重绘游戏区域
    this->DrawGameBoard();

    // 初始化网络
    WSADATA wsaData = { 0 };
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 连接服务器
    BOOL result = CSocket::Create(0, SOCK_STREAM, NULL);
    if (!result) {
        throw SOCKET_ERROR;
    }
    result = CSocket::Connect(GomokuProtocol::GetServerIP(), GomokuProtocol::SERVER_PORT);
    if (!result) {
        throw SOCKET_ERROR;
    }
}

/**
 * 析构方法。
 */
Game::~Game() {
    // 发送退出游戏请求
    QuitGame quitGame(mUsername);
    this->SendData(&quitGame, sizeof(quitGame));

    // 延迟一下，防止数据还没发送完成就关闭网络
    Sleep(100);

    // 关闭网络
    CSocket::Close();
    WSACleanup();
}

/**
 * 接受或拒绝挑战。
 * @param rivalName 对手的名字
 * @param accept    接受挑战则为 TRUE，否则为 FALSE
 */
void Game::AcceptInvite(LPCTSTR rivalName, BOOL accept) {
    InviteResult result(accept, mUsername, rivalName);

    // 如果接受则游戏开始
    if (accept) {
        mRivalName = rivalName;
        this->Start(FALSE);
    }

    // 发送结果
    this->SendData(&result, sizeof(InviteResult));
}

/**
 * 检查横向。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::CheckHorizontal() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量

    // 从左到右检查一行
    for (int i = 0; i < Const::GAME_BOARD_POINT_COUNT_H; i++) {
        // 查找棋子
        if (mPieces[mLastPos.y][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 检查斜向（从左上到右下）。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::CheckOblique1() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;                  // 要检查的棋子
    int count = 0;                                                      // 连续的棋子的数量
    const int min = mLastPos.x < mLastPos.y ? mLastPos.x: mLastPos.y;  // 范围坐标，用以确定左边界和上边界
    const int left = mLastPos.x - min;                                  // 左边界
    const int top = mLastPos.y - min;                                   // 上边界

    // 从左上到右下检查一斜行
    for (int i = left, j = top; i < Const::GAME_BOARD_POINT_COUNT_H && j < Const::GAME_BOARD_POINT_COUNT_V; i++, j++) {
        // 查找棋子
        if (mPieces[j][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 检查斜向（从左下到右上）。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::CheckOblique2() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量
    int left = 0;                                       // 左边界
    int bottom = 0;                                     // 下边界

    // 计算左边界和下边界
    if (mLastPos.x + mLastPos.y < Const::GAME_BOARD_POINT_COUNT_H) {
        bottom = mLastPos.x + mLastPos.y;
    } else {
        left = mLastPos.x + mLastPos.y - Const::GAME_BOARD_POINT_COUNT_H + 1;
        bottom = Const::GAME_BOARD_POINT_COUNT_V - 1;
    }

    // 从左上到右上检查一斜行
    for (int i = left, j = bottom; i < Const::GAME_BOARD_POINT_COUNT_H && j >= 0; i++, j--) {
        // 查找棋子
        if (mPieces[j][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 检查纵向。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::CheckVertical() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量

    // 从上到下检查一列
    for (int j = 0; j < Const::GAME_BOARD_POINT_COUNT_V; j++) {
        // 查找棋子
        if (mPieces[j][mLastPos.x] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 认输。
 */
void Game::Defeat() {
    // 发送游戏结束数据
    class GameOver gameOver(SELF_ADMIT, mRivalName);
    this->SendData(&gameOver, sizeof(class GameOver));

    // 游戏结束
    this->GameOver(SELF_ADMIT);
}

/**
 * 画游戏区域。
 */
void Game::DrawGameBoard() {
    // 画背景色
    mDrawer->FillSolidRect(0, 0, Const::GAME_BOARD_WIDTH, Const::GAME_BOARD_HEIGHT, Const::GAME_BOARD_COLOR);

    // 画线
    for (int i = 1; i <= Const::GAME_BOARD_POINT_COUNT_H; i++) {
        // 画横线
        mDrawer->MoveTo(Const::GRID_SIZE, i * Const::GRID_SIZE);
        mDrawer->LineTo(Const::GAME_BOARD_WIDTH - Const::GRID_SIZE, i * Const::GRID_SIZE);

        // 画竖线
        mDrawer->MoveTo(i * Const::GRID_SIZE, Const::GRID_SIZE);
        mDrawer->LineTo(i * Const::GRID_SIZE, Const::GAME_BOARD_HEIGHT - Const::GRID_SIZE);
    }

    // 通知游戏区域重绘
    mGameBoard->Invalidate();
}

/**
 * 寻找其它用户。
 */
void Game::FindUsers() {
    if (mUsername.IsEmpty()) {
        return;
    }

    FindUsersRequest request(mUsername);
    this->SendData(&request, sizeof(request));
}

/**
 * 游戏结束。
 * @param reason    结束原因
 */
void Game::GameOver(GameOverReason reason) {
    // 将状态设置为已结束
    mStatus = END;

    // 发送通知
    mCallback->SendMessage(Const::UM_GAME_OVER, reason);
}

/**
 * 处理寻找其它用户结果。
 * @param result    结果
 */
void Game::HandleFindUsers(FindUsersResult* result) {
    // 发送通知
    mCallback->SendMessage(Const::UM_GET_FIND_USERS_RESULT, (WPARAM)result, 0);
}

/**
 * 处理游戏结束。
 * @param gameOver  游戏结束信息
 */
void Game::HandleGameOver(class GameOver* gameOver) {
    // 如果没在下棋中，则直接返回
    if (mStatus == END) {
        return;
    }

    // 将状态设置为已结束
    mStatus = END;

    // 发送通知
    GameOverReason reason = gameOver->mReason;
    if (gameOver->mReason == WON) {
        reason = LOST;
    } else if (reason == SELF_ADMIT) {
        reason = RIVAL_ADMIT;
    }
    mCallback->SendMessage(Const::UM_GAME_OVER, reason);
}

/**
 * 处理挑战请求。
 * @param request   请求
 */
void Game::HandleInviteRequest(InviteRequest* request) {
    // 发送通知
    mCallback->SendMessage(Const::UM_GET_INVITE_REQUEST, (WPARAM)request->mRivalName, 0);
}

/**
 * 处理挑战结果。
 * @param result    结果
 */
void Game::HandleInviteResult(InviteResult* result) {
    // 如果接受则游戏开始
    if (result->mAccept) {
        mRivalName = result->mRivalName;
        this->Start(TRUE);
    }

    // 发送通知
    mCallback->SendMessage(Const::UM_GET_INVITE_RESULT, (WPARAM)result->mRivalName, (LPARAM)result->mAccept);
}

/**
 * 处理登录结果。
 * @param result    结果
 */
void Game::HandleLogon(LogonResult* result) {
    BOOL succeeded = result->mUsername[0] != '\0';
    if (succeeded) {
        // 保存用户名
        mUsername = result->mUsername;
    }

    // 发送通知
    mCallback->SendMessage(Const::UM_GET_LOGON_RESULT, (WPARAM)succeeded, 0);
}

/**
 * 处理放置棋子。
 * @param putPiece  棋子信息
 */
void Game::HandlePutPiece(PutPiece* putPiece) {
    // 放一个与自己颜色相反的棋子
    this->Put_Piece(!mIsBlack, putPiece->mX, putPiece->mY);

    // 将状态设置为游戏中
    mStatus = PLAYING;
}

/**
 * 处理对手退出游戏事件。
 * @param quitGame  退出游戏信息
 */
void Game::HandleQuitGame(QuitGame* quitGame) {
    // 如果不是当前对手，则直接返回
    if (mRivalName != quitGame->mUsername) {
        return;
    }

    // 发送通知
    BOOL isPlaying = mStatus != END;
    mCallback->SendMessage(Const::UM_RIVAL_QUIT_GAME, isPlaying, (LPARAM)quitGame->mUsername);

    // 对手名置空
    mRivalName.Empty();

    // 将状态设置为已结束
    mStatus = END;
}

/**
 * 发起挑战。
 * @param rivalName 要挑战的用户的名字
 */
void Game::Invite(LPCTSTR rivalName) {
    if (rivalName == NULL) {
        return;
    }

    // 发送挑战请求
    InviteRequest request(mUsername, rivalName);
    this->SendData(&request, sizeof(request));
}

/**
 * 判断是否胜利。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::IsWon() {
    // 检查横向
    BOOL result = this->CheckHorizontal();
    if (result) {
        return result;
    }

    // 检查纵向
    result = this->CheckVertical();
    if (result) {
        return result;
    }

    // 检查斜向（从左上到右下）
    result = this->CheckOblique1();
    if (result) {
        return result;
    }

    // 检查斜向（从左下到右上）
    return this->CheckOblique2();
}

/**
 * 登录。
 * @param username  用户名
 */
void Game::Logon(LPCTSTR username) {
    // 发送登录请求
    LogonRequest request(username);
    this->SendData(&request, sizeof(request));
}

/**
 * 游戏区域的单击响应方法。
 * @param x, y  用户单击的位置
 */
void Game::OnGameBoardClicked(int x, int y) {
    const int bound = Const::GRID_SIZE / 2;   // 格的尺寸的一半

    // 如果不是己方下棋中（对手下棋中或游戏结束）或者 单击的范围超出棋盘，则直接返回
    if (mStatus != PLAYING ||
        x < bound || x > Const::GAME_BOARD_WIDTH - bound ||
        y < bound || y > Const::GAME_BOARD_HEIGHT - bound) {
        return;
    }

    // 将用户单击的点转换为离得最近的棋盘点
    int tempX = x;
    int tempY = y;
    int temp = x % Const::GRID_SIZE;
    if (temp >= bound) {
        tempX += Const::GRID_SIZE - temp;
    } else {
        tempX -= temp;
    }

    temp = y % Const::GRID_SIZE;
    if (temp >= bound) {
        tempY += Const::GRID_SIZE - temp;
    } else {
        tempY -= temp;
    }

    // 将窗口坐标转换成棋盘坐标
    tempX = tempX / Const::GRID_SIZE - 1;
    tempY = tempY / Const::GRID_SIZE - 1;

    // 放置棋子
    BOOL put = this->Put_Piece(mIsBlack, tempX, tempY);
    if (!put) {
        return;
    }

    // 判断是否胜利
    BOOL won = this->IsWon();

    // 将已放置的棋子发给对手
    PutPiece putPiece(mRivalName, tempX, tempY);
    this->SendData(&putPiece, sizeof(putPiece));

    // 如果胜利则通知对手，否则等待对手下棋
    if (won) {
        // 发送游戏结束数据
        class GameOver gameOver(WON, mRivalName);
        this->SendData(&gameOver, sizeof(gameOver));

        // 游戏结束
        this->GameOver(WON);
    } else {
        mStatus = RIVAL_PLAYING;
    }
}

/**
 * 处理服务器发来的数据。
 * @param pro   协议
 */
void Game::OnGetMessageFromServer(GomokuProtocol* pro) {
    switch (pro->mType) {
        case GomokuProtocol::FIND_USERS:
            this->HandleFindUsers((FindUsersResult*)pro);
            break;
        case GomokuProtocol::GAME_OVER:
            this->HandleGameOver((class GameOver*)pro);
            break;
        case GomokuProtocol::INVITE_REQUEST:
            this->HandleInviteRequest((InviteRequest*)pro);
            break;
        case GomokuProtocol::INVITE_RESULT:
            this->HandleInviteResult((InviteResult*)pro);
            break;
        case GomokuProtocol::LOGON:
            this->HandleLogon((LogonResult*)pro);
            break;
        case GomokuProtocol::PUT_PIECE:
            this->HandlePutPiece((PutPiece*)pro);
            break;
        case GomokuProtocol::QUIT_GAME:
            this->HandleQuitGame((QuitGame*)pro);
            break;
    }
}

/**
 * 接收到数据的响应方法。
 * @param errorCode 错误码
 */
void Game::OnReceive(int errorCode) {
    // 接收数据长度
    int size = 0;
    int read = CSocket::Receive((char*)&size, sizeof(size));
    if (read == SOCKET_ERROR) {
        CSocket::OnReceive(errorCode);
        return;
    }

    // 接收数据
    char* buffer = new char[size];
    read = CSocket::Receive(buffer, size);
    if (read <= 0) {
        delete[] buffer;
        CSocket::OnReceive(errorCode);
        return;
    }

    // 将数据转换成协议
    GomokuProtocol* pro = (GomokuProtocol*)buffer;

    // 处理协议
    this->OnGetMessageFromServer(pro);

    // 释放缓存
    delete[] buffer;

    AsyncSelect(FD_READ);
    CSocket::OnReceive(errorCode);
}

/**
 * 放置棋子。
 * @param isBlack   黑棋则为 TRUE，白棋则为 FALSE
 * @param x, y      棋盘坐标
 * @return 成功则返回 TRUE，否则返回 FALSE
 */
BOOL Game::Put_Piece(BOOL isBlack, int x, int y) {
    // 如果超出棋盘区域，则返回 false
    if (x >= Const::GAME_BOARD_POINT_COUNT_H || y >= Const::GAME_BOARD_POINT_COUNT_V) {
        return FALSE;
    }

    // 如果当前点已经有棋子了，则返回 FALSE
    if (mPieces[y][x] != NOTHING) {
        return FALSE;
    }

    // 画实心圆
    if (isBlack) {
        mDrawer->SelectObject(&mBlackBrush);
    } else {
        mDrawer->SelectObject(&mWhiteBrush);
    }
    mDrawer->Ellipse((x + 1) * Const::GRID_SIZE - Const::PIECE_RADIUS, (y + 1) * Const::GRID_SIZE - Const::PIECE_RADIUS,
        (x + 1) * Const::GRID_SIZE + Const::PIECE_RADIUS, (y + 1) * Const::GRID_SIZE + Const::PIECE_RADIUS);

    // 通知游戏区域重绘
    mGameBoard->Invalidate();

    // 保存最后放置棋子的位置
    mLastPos.x = x;
    mLastPos.y = y;

    // 保存到所有棋子中
    if (isBlack) {
        mPieces[y][x] = BLACK;
    } else {
        mPieces[y][x] = WHITE;
    }

    return TRUE;
}

/**
 * 游戏重新开始。
 */
void Game::Restart() {
    // 发送挑战请求
    InviteRequest request(mUsername, mRivalName);
    this->SendData(&request, sizeof(request));
}

/**
 * 发送数据。
 * @param pro         协议
 * @param dataSize    数据长度
 */
void Game::SendData(GomokuProtocol* pro, int dataSize) {
    // 发送数据长度
    CSocket::Send(&dataSize, sizeof(dataSize), 0);

    // 发送数据
    CSocket::Send(pro, dataSize, 0);

    // 防止发送过快导致服务器无法响应
    Sleep(100);
}

/**
 * 游戏开始。
 * @param isBlack   黑棋则为 TRUE，白棋则为 FALSE
 */
void Game::Start(BOOL isBlack) {
    // 重绘游戏区域
    this->DrawGameBoard();

    // 清空所有棋子
    ZeroMemory(mPieces, sizeof(mPieces));

    // 设置成员变量
    mStatus = isBlack ? PLAYING: RIVAL_PLAYING;
    mIsBlack = isBlack;

    // 发送通知
    mCallback->SendMessage(Const::UM_GAME_START, (WPARAM)(LPCTSTR)mRivalName);
}
